/**
 * 
 */
package group.anglers.bpmn.core.workflow.impls;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import bsh.EvalError;
import bsh.Interpreter;
import group.anglers.bpmn.core.workflow.WorkflowContext;
import group.anglers.bpmn.core.workflow.WorkflowProcess;
import group.anglers.bpmn.general.context.ExecutionContext;

/**
 * @author an.pham
 * 
 */
public class WorkflowProcessImpl implements WorkflowProcess {
	private Node definitions;
	private XPath xpath;

	public WorkflowProcessImpl(Node definitions) {
		this.definitions = definitions;
		XPathFactory xPathfactory = XPathFactory.newInstance();
		this.xpath = xPathfactory.newXPath();
	}

	@Override
	public List<WorkflowContext> next(String currentNodeId, ExecutionContext executionContext) throws XPathExpressionException {
		XPathExpression xPathExpression = null;
		
		/* default is startEvent node */
		if (currentNodeId == null || currentNodeId.trim().isEmpty()) {
			xPathExpression = xpath.compile(WorkflowConstants.DEFAULT_PROCESS_NODE_PATH
					.concat(WorkflowConstants.START_EVENT_NODE));
		}else {
			/* find node by given currentNodeId */
			xPathExpression = xpath.compile(WorkflowConstants.DEFAULT_PROCESS_NODE_PATH
					.concat("node()[@id='")
					.concat(currentNodeId)
					.concat("']"));
		}
		
		Node currentNode = (Node) xPathExpression.evaluate(definitions, XPathConstants.NODE);
		
		return next(currentNode, executionContext);
		// TODO
	}

	private List<WorkflowContext> next(Node currentNode, ExecutionContext executionContext) throws XPathExpressionException {
		XPathExpression xPathExpression = xpath.compile(WorkflowConstants.DEFAULT_PROCESS_NODE_PATH
					.concat("sequenceFlow[@sourceRef='"
					.concat(currentNode.getAttributes().getNamedItem("id").getNodeValue())
					.concat("']")));
		
		NodeList sequenceFlows = (NodeList) xPathExpression.evaluate(definitions, XPathConstants.NODESET);
		
		if(sequenceFlows != null){
			Node sequenceFlow = null;
			Node childNode = null;
			NodeList childNodes = null;
			
			for(int i=0; i < sequenceFlows.getLength(); i ++){
				sequenceFlow = sequenceFlows.item(i);
				if(sequenceFlow.hasChildNodes()){
					Interpreter interpreter = new Interpreter();
					
					childNodes = sequenceFlow.getChildNodes();
					// loop to find conditionExpression node
					for(int y=0; y < childNodes.getLength(); y ++){
						childNode = sequenceFlow.getChildNodes().item(y);
						Object eval = false;
						if(childNode.getNodeType() != Node.TEXT_NODE){
							try {
								eval = interpreter.eval(childNode.getTextContent());

								if(eval != null && Boolean.parseBoolean(eval.toString())){
									return getTargetNodeBySequenceFlow(sequenceFlow, executionContext);
								}
							} catch (EvalError e) {
								e.printStackTrace();
							}
						}
					}
				}else{
					return getTargetNodeBySequenceFlow(sequenceFlow, executionContext);
				}
			}
		}
		
		//TODO there is no sequenceFlows so that means the process is not valid
		return null;
	}
	
	private List<WorkflowContext> getTargetNodeBySequenceFlow(Node sequenceFlow, ExecutionContext executionContext) throws XPathExpressionException {
		Node childNode = null;
		NodeList childNodes = null;
		WorkflowContext workflowContext = null;
		String dataInputName = null;
		
		List<WorkflowContext> workflowContexts = new ArrayList<WorkflowContext>();
		HashMap<String, String> userDefinedAttrs = new HashMap<String, String>();
		
		XPathExpression xPathExpression = xpath.compile(WorkflowConstants.DEFAULT_PROCESS_NODE_PATH
				 .concat("node()[@id='")
				 .concat(sequenceFlow.getAttributes().getNamedItem("targetRef").getNodeValue())
				 .concat("']"));
		
		Node targetRefNode  = (Node) xPathExpression.evaluate(definitions, XPathConstants.NODE);
		/* if node type is UserTask */
		if(targetRefNode.getNodeName().equals(WorkflowConstants.USER_TASK_NODE)){
			// find dataInput nodes
			xPathExpression = xpath.compile("//userTask/ioSpecification/dataInput");
			
			childNodes = (NodeList) xPathExpression.evaluate(targetRefNode, XPathConstants.NODESET);
			
			workflowContext = new WorkflowContext();
			/* set required attributes */
			workflowContext.setNodeId(targetRefNode.getAttributes().getNamedItem("id").getNodeValue());
			workflowContext.setName(targetRefNode.getAttributes().getNamedItem("name").getNodeValue());
			/* get potentialOwner */
			xPathExpression = xpath.compile("//userTask"
					.concat("[@id='").concat(workflowContext.getNodeId()).concat("']")
					.concat("/potentialOwner/resourceAssignmentExpression/formalExpression"));
			
			childNode = (Node) xPathExpression.evaluate(targetRefNode, XPathConstants.NODE);
			
			if(childNode !=  null){
				workflowContext.setActorId(childNode.getTextContent());
			}
			
			/* check if user defined attributes are provided */
			if(childNodes != null){
				for(int x=0; x < childNodes.getLength() ; x ++){
					childNode = childNodes.item(x);
					// get dataInputName
					dataInputName = childNode.getAttributes().getNamedItem("name").getNodeValue();
					// find dataInputValue
					xPathExpression = xpath.compile(
							"//userTask/dataInputAssociation[targetRef='"
							.concat( childNode.getAttributes().getNamedItem("id").getNodeValue())
							.concat("']/assignment/from"));
					
					childNode = (Node) xPathExpression.evaluate(targetRefNode, XPathConstants.NODE);
					
					if(dataInputName.equals(WorkflowConstants.GROUP_ID)){
						workflowContext.setGroupId(childNode.getTextContent());
					}else{
						userDefinedAttrs.put(dataInputName, childNode.getTextContent());
					}
				}
				// set userDefinedAttrs
				workflowContext.setUserDefinedAttrs(userDefinedAttrs);
			}
			// add workflowContext
			workflowContexts.add(workflowContext);
		} else if(targetRefNode.getNodeName().equals(WorkflowConstants.INCLUSIVE_GATEWAY_NODE)){
			workflowContexts = next(targetRefNode, executionContext);
		}
		
		return workflowContexts;
	}
}
